﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Drawing;

namespace CSW.Framework.UI.UserControls
{
    /// <summary>
    /// HighlightRichTextBox
    /// </summary>
    public class HighlightRichTextBox : RichTextBox
    {
        private readonly Dictionary<string, Color> m_Keywords = new Dictionary<string, Color>();
        //private const bool m_bPaint = true;
        //private string m_strLine = "";
        //private int m_nContentLength = 0;
        //private int m_nLineLength;
        //private int m_nLineStart;
        //private int m_nLineEnd;
        //private string m_strKeywords = "";
        //private int m_nCurSelection = 0;

        /// <summary>
        /// A list containing all keywords.
        /// </summary>
        public Dictionary<string, Color> Keywords
        {
            get { return m_Keywords; }
        }

        /*/// <summary>
        /// WndProc
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x00f)
            {
                if (m_bPaint)
                    base.WndProc(ref m);
                else
                    m.Result = IntPtr.Zero;
            }
            else
                base.WndProc(ref m);
        }*/

        /*/// <summary>
        /// OnTextChanged
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            // Calculate here.
            m_nContentLength = this.TextLength;

            int nCurrentSelectionStart = SelectionStart;
            int nCurrentSelectionLength = SelectionLength;

            m_bPaint = false;

            // Find the start of the current line.
            m_nLineStart = nCurrentSelectionStart;
            while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != '\n'))
                m_nLineStart--;
            // Find the end of the current line.
            m_nLineEnd = nCurrentSelectionStart;
            while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != '\n'))
                m_nLineEnd++;
            // Calculate the length of the line.
            m_nLineLength = m_nLineEnd - m_nLineStart;
            // Get the current line.
            m_strLine = Text.Substring(m_nLineStart, m_nLineLength);

            // Process this line.
            ProcessLine();

            m_bPaint = true;
        }*/

        /*/// <summary>
        /// Process a line.
        /// </summary>
        private void ProcessLine()
        {
            // Save the position and make the whole line black
            int nPosition = SelectionStart;
            SelectionStart = m_nLineStart;
            SelectionLength = m_nLineLength;
            SelectionColor = Color.Black;

            // Process the keywords
            ProcessRegex();

            SelectionStart = nPosition;
            SelectionLength = 0;
            SelectionColor = Color.Black;

            m_nCurSelection = nPosition;
        }*/

        /*/// <summary>
        /// Process a regular expression.
        /// </summary>
        private void ProcessRegex()
        {
            foreach (KeyValuePair<string, Color> kvp in m_Keywords)
            {
                string strRegex = "\\b" + kvp.Key + "\\b";
                Color color = kvp.Value;

                Regex regKeywords = new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled);

                for (Match regMatch = regKeywords.Match(m_strLine); regMatch.Success; regMatch = regMatch.NextMatch())
                {
                    // Process the words
                    int nStart = m_nLineStart + regMatch.Index;
                    int nLength = regMatch.Length;
                    SelectionStart = nStart;
                    SelectionLength = nLength;
                    SelectionColor = color;
                }
            }
        }*/

        /*/// <summary>
        /// Compiles the keywords as a regular expression.
        /// </summary>
        public void CompileKeywords()
        {
            for (int i = 0; i < Settings.Keywords.Count; i++)
            {
                string strKeyword = Settings.Keywords[i];

                if (i == Settings.Keywords.Count - 1)
                    m_strKeywords += "\\b" + strKeyword + "\\b";
                else
                    m_strKeywords += "\\b" + strKeyword + "\\b|";
            }
        }*/

        /*/// <summary>
        /// Processes all lines.
        /// </summary>
        public void ProcessAllLines()
        {
            m_bPaint = false;
            SuspendLayout();

            try
            {
                int nStartPos = 0;
                int i = 0;
                while (i < Lines.Length)
                {
                    m_strLine = Lines[i];
                    m_nLineStart = nStartPos;
                    m_nLineEnd = m_nLineStart + m_strLine.Length;

                    ProcessLine();
                    i++;

                    nStartPos += m_strLine.Length + 1;
                }
            }
            finally
            {
                m_bPaint = true;
                ResumeLayout();
            }

            string rtf = Rtf;
        }*/

        /// <summary>
        /// HighlightPoint
        /// </summary>
        public class HighlightPoint
        {
            /// <summary>
            /// Gets the index of the line.
            /// </summary>
            /// <value>The index of the line.</value>
            public int LineIndex { get; private set; }

            /// <summary>
            /// Gets the text offset.
            /// </summary>
            /// <value>The text offset.</value>
            public int TextOffset { get; private set; }

            /// <summary>
            /// Gets the length of the text.
            /// </summary>
            /// <value>The length of the text.</value>
            public int TextLength { get; private set; }

            /// <summary>
            /// Gets the color.
            /// </summary>
            /// <value>The color.</value>
            public Color Color { get; private set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="HighlightPoint"/> class.
            /// </summary>
            /// <param name="lineIndex">Index of the line.</param>
            /// <param name="textOffset">The text offset.</param>
            /// <param name="textLength">Length of the text.</param>
            /// <param name="color">The color.</param>
            public HighlightPoint(int lineIndex, int textOffset, int textLength, Color color)
            {
                LineIndex = lineIndex;
                TextOffset = textOffset;
                TextLength = textLength;
                Color = color;
            }
        }

        /// <summary>
        /// Sets the text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="highlightPoints">The highlight points.</param>
        public void SetText(string text, List<HighlightPoint> highlightPoints)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(@"{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}");
            sb.AppendLine(@"{\colortbl ;\red0\green0\blue0;\red255\green0\blue0;\red0\green0\blue255;\red0\green255\blue0;\red255\green140\blue0;}");
            sb.Append(@"\viewkind4\uc1\pard\cf1\lang1033\f0\fs20 ");

            Dictionary<KeyValuePair<string, Color>, Regex> regex = new Dictionary<KeyValuePair<string, Color>, Regex>();
            foreach (KeyValuePair<string, Color> kvp in m_Keywords)
            {
                string strRegex = "\\b" + kvp.Key + "\\b";
                regex.Add(kvp, new Regex(strRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled));
            }

            string[] lines = text.Replace("\r", "").Split(new[] { '\n' }, StringSplitOptions.None);
            for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++)
            {
                string line = lines[lineIndex];

                sb.AppendLine(string.Format(@"{0}\par", GetFormattedLine(regex, highlightPoints, line, lineIndex)));
            }
            
            sb.AppendLine(@"\par");
            sb.AppendLine(@"}");

            Rtf = sb.ToString();
        }

        private static string GetFormattedLine(Dictionary<KeyValuePair<string, Color>, Regex> keywords, List<HighlightPoint> highlightPoints, string line, int lineIndex)
        {
            int offset = 0;
            int lastTextOffset = -1;
            foreach (HighlightPoint point in highlightPoints.Where(p => p.LineIndex == lineIndex).OrderBy(p => p.TextOffset))
            {
                string colorText = GetColorText(point.Color);

                if (lastTextOffset == point.TextOffset)
                    continue;
                lastTextOffset = point.TextOffset;

                int start = offset + point.TextOffset;
                int length = point.TextLength;

                line = line.Insert(start + length, @"\highlight0 ");
                line = line.Insert(start, colorText);

                offset += 24;
            }

            foreach (KeyValuePair<KeyValuePair<string, Color>, Regex> kvp in keywords)
            {
                string colorText = GetColorText(kvp.Key.Value);

                Regex regKeywords = kvp.Value;

                offset = 0;
                for (Match regMatch = regKeywords.Match(line); regMatch.Success; regMatch = regMatch.NextMatch())
                {
                    int start = offset + regMatch.Index;
                    int length = offset + regMatch.Length;

                    if (start >= 12)
                    {
                        // ignore text already colorized
                        string colorizedCheck = line.Substring(start - 12, 10);
                        if (colorizedCheck == @"\highlight")
                            continue;
                    }

                    line = line.Insert(start + length, @"\highlight0 ");
                    line = line.Insert(start, colorText);

                    offset += 24;
                }
            }

            return line;
        }

        private static string GetColorText(Color color)
        {
            string colorText;

            if (color == Color.Red)
                colorText = @"\highlight2 ";
            else if (color == Color.Blue)
                colorText = @"\highlight3 ";
            else if (color == Color.Green)
                colorText = @"\highlight4 ";
            else if (color == Color.Orange)
                colorText = @"\highlight5 ";
            else
                throw new NotSupportedException("Only Red/Green/Blue/Orange are supported at this time.");

            return colorText;
        }
    }
}
